#!/usr/bin/env python

import sys,os,unittest,platform
import ctypes,struct,time

def progress():
  p=["-\b","/\b","|\b","\\\b",]
  index=0
  while True:
    yield p[index]
    index = (index+1)%len(p)

BUFF_SIZE=1024
MAX_ERROR_COUNT=3
AAC_MAX_CHANNELS=8
DOWN_CHANNEL=2

class BasicTest(unittest.TestCase):
  def setUp(self):
    pass

  def tearDown(self):
    pass

# TEST 01 ----------------------------------------- #
  def test01_ImportPyfaad(self):
    import pyfaad

# TEST 02 ----------------------------------------- #
  def test02_ImportPyfaadFaad(self):
    import pyfaad.faad

# TEST 03 ----------------------------------------- #
  def test03_FaadOpenClose(self):
    from pyfaad import faad
    hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(hDecoder, -1)
    faad.NeAACDecClose(hDecoder)

# TEST 04 ----------------------------------------- #
  def test04_FaadConfiguration(self):
    from pyfaad import faad
    hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(hDecoder, -1)

    pConfig = faad.NeAACDecGetCurrentConfiguration(hDecoder)
    pConfig.contents.defObjectType = faad.MAIN
    pConfig.contents.outputFormat = faad.FAAD_FMT_16BIT
    rtn = faad.NeAACDecSetConfiguration(hDecoder, pConfig)
    self.assertNotEqual(rtn, 0,"Error, invalid configuration.")

    faad.NeAACDecClose(hDecoder)

# TEST 05 ----------------------------------------- #
  def test05_FaadInitWithAac(self):
    global AAC_MAX_CHANNELS

    from pyfaad import faad
    hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(hDecoder, -1)

    pConfig = faad.NeAACDecGetCurrentConfiguration(hDecoder)
    pConfig.contents.defObjectType = faad.MAIN
    pConfig.contents.outputFormat = faad.FAAD_FMT_16BIT
    rtn = faad.NeAACDecSetConfiguration(hDecoder, pConfig)
    self.assertNotEqual(rtn, 0,"Error, invalid configuration.")

    fp=open("test.aac","rb")
    s=fp.read(faad.FAAD_MIN_STREAMSIZE*AAC_MAX_CHANNELS)

    mReadSize=ctypes.c_size_t(len(s))
    cBuffer=ctypes.cast(ctypes.create_string_buffer(s,len(s)),ctypes.POINTER(ctypes.c_ubyte))

    ulSamplerate=ctypes.c_ulong()
    ubChannels=ctypes.c_ubyte()
    read_bytes=faad.NeAACDecInit(hDecoder, cBuffer, mReadSize,
      ctypes.pointer(ulSamplerate), ctypes.pointer(ubChannels))

    self.assertTrue(0<=read_bytes)
    sys.stderr.write(" <rate:%s,channels:%s> "%(ulSamplerate.value,ubChannels.value))

    faad.NeAACDecClose(hDecoder)
    fp.close()


# TEST 06 ----------------------------------------- #
  def test06_FaadDecodeAac(self):
    global AAC_MAX_CHANNELS

    global BUFF_SIZE
    global MAX_ERROR_COUNT

    from pyfaad import faad
    hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(hDecoder, -1)

    pConfig = faad.NeAACDecGetCurrentConfiguration(hDecoder)
    pConfig.contents.defObjectType = faad.MAIN
    pConfig.contents.outputFormat = faad.FAAD_FMT_16BIT
    rtn = faad.NeAACDecSetConfiguration(hDecoder, pConfig)
    self.assertNotEqual(rtn, 0,"Error, invalid configuration.")

    fp=open("test.aac","rb")
    s=fp.read(faad.FAAD_MIN_STREAMSIZE*AAC_MAX_CHANNELS)

    mReadSize=ctypes.c_size_t(len(s))
    cBuffer=ctypes.cast(ctypes.create_string_buffer(s,len(s)),ctypes.POINTER(ctypes.c_ubyte))

    ulSamplerate=ctypes.c_ulong()
    ubChannels=ctypes.c_ubyte()
    read_bytes=faad.NeAACDecInit(hDecoder, cBuffer, mReadSize,
      ctypes.pointer(ulSamplerate), ctypes.pointer(ubChannels))

    self.assertTrue(0<=read_bytes)
    sys.stderr.write(" <rate:%s,channels:%s> "%(ulSamplerate.value,ubChannels.value))

    mFrameInfo = faad.NeAACDecFrameInfo()
    s = s[read_bytes:]

    s+=fp.read(BUFF_SIZE)
    error_count=0
    p=progress()

    while len(s):
      mReadSize=ctypes.c_size_t(len(s))
      cBuffer=ctypes.cast(ctypes.create_string_buffer(s,len(s)),ctypes.POINTER(ctypes.c_ubyte))
      vpDecbuffer = faad.NeAACDecDecode(hDecoder, ctypes.pointer(mFrameInfo), cBuffer, mReadSize)

      if mFrameInfo.error!=0:
        sys.stderr.write(" <samples:%s(%sB/%sB) %s,%s> "%(
          mFrameInfo.samples,mFrameInfo.bytesconsumed,mReadSize.value,
          mFrameInfo.error,faad.NeAACDecGetErrorMessage(mFrameInfo.error)
        ))
        error_count +=1
        if error_count>MAX_ERROR_COUNT:
          sys.stderr.write(" <aborting decode> ")
          break
      else:
        sys.stderr.write(p.next())
        error_count =0

      s = s[mFrameInfo.bytesconsumed:]
      s+=fp.read(BUFF_SIZE)

    faad.NeAACDecClose(hDecoder)
    fp.close()

# TEST 07 ----------------------------------------- #
  def test07_FaadDecodeAacIntoWav(self):
    import wave
    w=wave.open("test.wav","wb")

    global AAC_MAX_CHANNELS

    global BUFF_SIZE
    global MAX_ERROR_COUNT

    global DOWN_CHANNEL

    from pyfaad import faad
    hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(hDecoder, -1)

    pConfig = faad.NeAACDecGetCurrentConfiguration(hDecoder)
    pConfig.contents.defObjectType = faad.MAIN
    pConfig.contents.outputFormat = faad.FAAD_FMT_16BIT
    pConfig.contents.downMatrix = DOWN_CHANNEL-1 # 2ch
    rtn = faad.NeAACDecSetConfiguration(hDecoder, pConfig)
    self.assertNotEqual(rtn, 0,"Error, invalid configuration.")

    fp=open("test.aac","rb")
    s=fp.read(faad.FAAD_MIN_STREAMSIZE*AAC_MAX_CHANNELS)

    mReadSize=ctypes.c_size_t(len(s))
    cBuffer=ctypes.cast(ctypes.create_string_buffer(s,len(s)),ctypes.POINTER(ctypes.c_ubyte))

    ulSamplerate=ctypes.c_ulong()
    ubChannels=ctypes.c_ubyte()
    read_bytes=faad.NeAACDecInit(hDecoder, cBuffer, mReadSize,
      ctypes.pointer(ulSamplerate), ctypes.pointer(ubChannels))

    self.assertTrue(0<=read_bytes)
    sys.stderr.write(" <rate:%s,channels:%s> "%(ulSamplerate.value,ubChannels.value))

    w.setcomptype("NONE", "Uncompressed")
    w.setframerate(ulSamplerate.value)
    #w.setnchannels(ubChannels.value)
    w.setnchannels(DOWN_CHANNEL)
    w.setsampwidth(2) # faad.FAAD_FMT_16BIT(16 bit integers) -> 2Bytes(?)

    mFrameInfo = faad.NeAACDecFrameInfo()
    s = s[read_bytes:]

    s+=fp.read(BUFF_SIZE)
    error_count=0
    p=progress()

    while len(s):
      mReadSize=ctypes.c_size_t(len(s))
      cBuffer=ctypes.cast(ctypes.create_string_buffer(s,len(s)),ctypes.POINTER(ctypes.c_ubyte))
      vpDecbuffer = faad.NeAACDecDecode(hDecoder, ctypes.pointer(mFrameInfo), cBuffer, mReadSize)

      if mFrameInfo.samples>0:
        b=ctypes.cast(vpDecbuffer,ctypes.POINTER(ctypes.c_ubyte*(mFrameInfo.samples * DOWN_CHANNEL)))
        w.writeframes( struct.pack(*(["%dB"%len(b.contents)]+b.contents[0:])) )

      if mFrameInfo.error!=0:
        sys.stderr.write(" <samples:%s(%sB/%sB) %s,%s> "%(
          mFrameInfo.samples,mFrameInfo.bytesconsumed,mReadSize.value,
          mFrameInfo.error,faad.NeAACDecGetErrorMessage(mFrameInfo.error)
        ))
        error_count +=1
        if error_count>MAX_ERROR_COUNT:
          sys.stderr.write(" <aborting decode> ")
          break
      else:
        sys.stderr.write(p.next())
        error_count =0

      s = s[mFrameInfo.bytesconsumed:]
      s+=fp.read(BUFF_SIZE)

    faad.NeAACDecClose(hDecoder)
    fp.close()
    w.close()





class PlaySoundTest(unittest.TestCase):
  def setUp(self):
    global AAC_MAX_CHANNELS
    global DOWN_CHANNEL

    from pyfaad import faad
    self.hDecoder = faad.NeAACDecOpen()
    self.assertNotEqual(self.hDecoder, -1)

    pConfig = faad.NeAACDecGetCurrentConfiguration(self.hDecoder)
    pConfig.contents.defObjectType = faad.MAIN
    pConfig.contents.outputFormat = faad.FAAD_FMT_16BIT
    pConfig.contents.downMatrix = DOWN_CHANNEL-1 # 2ch
    rtn = faad.NeAACDecSetConfiguration(self.hDecoder, pConfig)
    self.assertNotEqual(rtn, 0,"Error, invalid configuration.")

    self.fp=open("test.aac","rb")
    self.s=self.fp.read(faad.FAAD_MIN_STREAMSIZE*AAC_MAX_CHANNELS)

    mReadSize=ctypes.c_size_t(len(self.s))
    cBuffer=ctypes.cast(ctypes.create_string_buffer(self.s,len(self.s)),ctypes.POINTER(ctypes.c_ubyte))

    self.ulSamplerate=ctypes.c_ulong()
    ubChannels=ctypes.c_ubyte()
    read_bytes=faad.NeAACDecInit(self.hDecoder, cBuffer, mReadSize,
      ctypes.pointer(self.ulSamplerate), ctypes.pointer(ubChannels))

    self.assertTrue(0<=read_bytes)
    sys.stderr.write(" <rate:%s,channels:%s> "%(self.ulSamplerate.value,ubChannels.value))

    self.s = self.s[read_bytes:]


  def tearDown(self):
    from pyfaad import faad
    faad.NeAACDecClose(self.hDecoder)
    self.fp.close()

# TEST 01 ----------------------------------------- #
  def test01_PlayDecodedAacOnMac(self):
    if platform.system()!="Darwin":
      sys.stderr.write(" <skiped> ")
      return
      
    global BUFF_SIZE
    global MAX_ERROR_COUNT
    global DOWN_CHANNEL

    from pyfaad import faad
    import Carbon.Sound
    Carbon.Sound.extSH=-5
    import audiodev
    dev = audiodev.AudioDev()

    dev.setsampwidth(DOWN_CHANNEL*16/8)
    #dev.setsampwidth(2)
    dev.setnchannels(DOWN_CHANNEL)
    #dev.setoutrate(self.ulSamplerate.value)
    dev.setoutrate(48000)

    mFrameInfo = faad.NeAACDecFrameInfo()

    self.s+=self.fp.read(BUFF_SIZE)
    error_count=0

    while len(self.s):
      mReadSize=ctypes.c_size_t(len(self.s))
      cBuffer=ctypes.cast(ctypes.create_string_buffer(self.s,len(self.s)),ctypes.POINTER(ctypes.c_ubyte))
      vpDecbuffer = faad.NeAACDecDecode(self.hDecoder, ctypes.pointer(mFrameInfo), cBuffer, mReadSize)

      if mFrameInfo.samples>0:
        #b=ctypes.cast(vpDecbuffer,ctypes.POINTER(ctypes.c_ubyte*(mFrameInfo.samples * DOWN_CHANNEL)))
        #dev.writeframes( struct.pack(*(["%dB"%len(b.contents)]+b.contents[0:])) )
        b=ctypes.cast(vpDecbuffer,ctypes.POINTER(ctypes.c_ubyte*(mFrameInfo.samples * DOWN_CHANNEL)))
        #print b.contents[0:],len(struct.pack(*(["<%dB"%len(b.contents)]+b.contents[0:]))) 
        dev.writeframes( struct.pack(*(["%dB"%len(b.contents)]+b.contents[0:])) )
        #dev.writeframes( "abcdiiiiiiiiiiii" )

      if mFrameInfo.error!=0:
        #sys.stderr.write(" <samples:%s(%sB/%sB) %s,%s> "%(
        #  mFrameInfo.samples,mFrameInfo.bytesconsumed,mReadSize.value,
        #  mFrameInfo.error,faad.NeAACDecGetErrorMessage(mFrameInfo.error)
        #))
        error_count +=1
        if error_count>MAX_ERROR_COUNT:
          #sys.stderr.write(" <aborting decode> ")
          break
      else:
        error_count =0

      self.s = self.s[mFrameInfo.bytesconsumed:]
      self.s+=self.fp.read(BUFF_SIZE)

    dev.wait()


# TEST 02 ----------------------------------------- #
  def test02_PlayDecodedAacOnLinux(self):
    if platform.system()!="Linux":
      sys.stderr.write(" <skiped> ")
      return

    global BUFF_SIZE
    global MAX_ERROR_COUNT
    global DOWN_CHANNEL

    from pyfaad import faad
    dev=None
    try:
      import ossaudiodev
      dev = ossaudiodev.open("w")
      dev.setfmt(ossaudiodev.AFMT_S16_LE)
      dev.channels(DOWN_CHANNEL)
      dev.speed(self.ulSamplerate.value)
    except:
      sys.stderr.write("warning: ossaudiodev init failed.\n")
      try:
        import alsaaudio
        dev = alsaaudio.PCM()
        dev.setformat(alsaaudio.PCM_FORMAT_S16_LE)
        dev.setchannels(DOWN_CHANNEL)
        dev.setrate(self.ulSamplerate.value)
      except:
        sys.stderr.write("warning: alsaaudio init failed.\n")
    
    self.assertNotEqual(dev, None, "Error, dev init failed.")

    mFrameInfo = faad.NeAACDecFrameInfo()

    self.s+=self.fp.read(BUFF_SIZE)
    error_count=0

    while len(self.s):
      mReadSize=ctypes.c_size_t(len(self.s))
      cBuffer=ctypes.cast(ctypes.create_string_buffer(self.s,len(self.s)),ctypes.POINTER(ctypes.c_ubyte))
      vpDecbuffer = faad.NeAACDecDecode(self.hDecoder, ctypes.pointer(mFrameInfo), cBuffer, mReadSize)

      if mFrameInfo.samples>0:
        b=ctypes.cast(vpDecbuffer,ctypes.POINTER(ctypes.c_ubyte*(mFrameInfo.samples * DOWN_CHANNEL)))
        dev.write( struct.pack(*(["%dB"%len(b.contents)]+b.contents[0:])) )

      if mFrameInfo.error!=0:
        #sys.stderr.write(" <samples:%s(%sB/%sB) %s,%s> "%(
        #  mFrameInfo.samples,mFrameInfo.bytesconsumed,mReadSize.value,
        #  mFrameInfo.error,faad.NeAACDecGetErrorMessage(mFrameInfo.error)
        #))
        error_count +=1
        if error_count>MAX_ERROR_COUNT:
          #sys.stderr.write(" <aborting decode> ")
          break
      else:
        error_count =0

      self.s = self.s[mFrameInfo.bytesconsumed:]
      self.s+=self.fp.read(BUFF_SIZE)

    dev.close()

# TEST 03 ----------------------------------------- #
  def test02_PlayDecodedAacOnWindows(self):
    if platform.system()!="Windows":
      sys.stderr.write(" <skiped> ")
      return

    global BUFF_SIZE
    global MAX_ERROR_COUNT
    global DOWN_CHANNEL

    from pyfaad import faad
    from pyfaad.pcmplayer import MMSystem

    hwaveout = MMSystem.HWAVEOUT()
    wavefx = MMSystem.WAVEFORMATEX(
      MMSystem.WAVE_FORMAT_PCM,
      DOWN_CHANNEL,              # nChannels
      self.ulSamplerate.value,   # SamplesPerSec
      self.ulSamplerate.value*16,# AvgBytesPerSec = 44100 SamplesPerSec * 16 wBitsPerSample
      DOWN_CHANNEL*16/8,         # nBlockAlign = 2 nChannels * 16 wBitsPerSample / 8 bits per byte
      16,                        # wBitsPerSample
      0
    )

    import Queue
    queue=Queue.Queue()
    wavehdrs=[]
    def waveProc(hWave,uMsg,dwUser,dw1,dw2):
      if uMsg==MMSystem.MM_WOM_OPEN:
        sys.stderr.write( " <Wave Opened> " )
      elif uMsg==MMSystem.MM_WOM_CLOSE:
        sys.stderr.write( " <Wave Closed> " )
      elif uMsg==MMSystem.MM_WOM_DONE:
        #sys.stderr.write( " <Wave Done> " )
        wavehdr_fin=queue.get()
        wavehdrs.append(wavehdr_fin)
      else:
        sys.stderr.write( " <Unknown %s> "%uMsg )

    func_type=ctypes.WINFUNCTYPE(None,MMSystem.HWAVEOUT, ctypes.c_uint, MMSystem.DWORD, MMSystem.DWORD, MMSystem.DWORD)
    func_pointer=func_type(waveProc)

    ret = MMSystem.waveOutOpen(
      ctypes.byref(hwaveout),     # buffer to receive a handle identifying
                                  # the open waveform-audio output device
      MMSystem.WAVE_MAPPER,       # constant to point to default wave device
      ctypes.byref(wavefx),       # identifier for data format sent for device
      ctypes.cast(ctypes.pointer(func_pointer), ctypes.POINTER(MMSystem.DWORD)).contents,               # DWORD_PTR dwCallback - callback mechanizm
      0,                          # DWORD_PTR dwCallbackInstance - user instance data for callback
      MMSystem.CALLBACK_FUNCTION  # DWORD fdwOpen - flag for opening the device
    )

    self.assertEqual(ret, MMSystem.MMSYSERR_NOERROR,
      "Error opening default waveform audio device (%s)"%ret)


    mFrameInfo = faad.NeAACDecFrameInfo()

    self.s+=self.fp.read(BUFF_SIZE)
    error_count=0

    while len(self.s):
      mReadSize=ctypes.c_size_t(len(self.s))
      cBuffer=ctypes.cast(ctypes.create_string_buffer(self.s,len(self.s)),ctypes.POINTER(ctypes.c_ubyte))
      vpDecbuffer = faad.NeAACDecDecode(self.hDecoder, ctypes.pointer(mFrameInfo), cBuffer, mReadSize)

      if mFrameInfo.samples>0:
        while queue.qsize()>10:
          time.sleep(0.1)

        wavehdr=MMSystem.WAVEHDR()
        wavehdr.lpData = vpDecbuffer
        wavehdr.dwBufferLength = mFrameInfo.samples * DOWN_CHANNEL
        queue.put(wavehdr)

        MMSystem.waveOutPrepareHeader(hwaveout, ctypes.byref(wavehdr), ctypes.sizeof(MMSystem.WAVEHDR))
        #print wavehdr,
        ret = MMSystem.waveOutWrite(hwaveout, ctypes.byref(wavehdr), ctypes.sizeof(MMSystem.WAVEHDR));
        #print ret
        self.assertEqual(ret, MMSystem.MMSYSERR_NOERROR,
          "Error waveOutWrite (%s)"%ret)

      if mFrameInfo.error!=0:
        #sys.stderr.write(" <samples:%s(%sB/%sB) %s,%s> "%(
        #  mFrameInfo.samples,mFrameInfo.bytesconsumed,mReadSize.value,
        #  mFrameInfo.error,faad.NeAACDecGetErrorMessage(mFrameInfo.error)
        #))
        error_count +=1
        if error_count>MAX_ERROR_COUNT:
          #sys.stderr.write(" <aborting decode> ")
          break
      else:
        error_count =0

      self.s = self.s[mFrameInfo.bytesconsumed:]
      self.s+=self.fp.read(BUFF_SIZE)

    for wavehdr_fin in wavehdrs:
      MMSystem.waveOutUnprepareHeader(hwaveout, ctypes.byref(wavehdr_fin), ctypes.sizeof(MMSystem.WAVEHDR))

    MMSystem.waveOutClose(hwaveout)


def runall():
  suites=[]
#  suites.append(unittest.TestLoader().loadTestsFromTestCase(BasicTest))
  suites.append(unittest.TestLoader().loadTestsFromTestCase(PlaySoundTest))

  runner = unittest.TextTestRunner(verbosity=2)

  for s in suites:
    runner.run(s)


if __name__=="__main__":
  current_path = os.path.abspath(os.curdir)
  os.chdir(os.path.dirname(os.path.abspath(__file__)))

  sys.path.insert(0,".."+os.sep)
  runall()
  os.chdir(current_path)




